/***********************************************************************
    filename:   CEGUIDirect3D9Texture.cpp
    created:    Mon Feb 9 2009
    author:     Paul D Turner
*************************************************************************/

#define NOMINMAX
#include <d3dx9.h>
#include "hgeTexture.h"
#include "hgeExceptions.h"
#include "hgeGuiSystem.h"

//----------------------------------------------------------------------------//
Texture::Texture(Renderer& owner) :
    d_owner(owner),
    d_texture(0),
    d_savedSurfaceDescValid(false)
{
	d_size.d_x = 0, d_size.d_y = 0;
	d_dataSize.d_x = 0, d_dataSize.d_y = 0;
	d_texelScaling.d_x = 1.0, d_texelScaling.d_y = 1.0;
}

//----------------------------------------------------------------------------//
Texture::Texture(Renderer& owner,
                                   const String& filename) :
    d_owner(owner),
    d_texture(0),
    d_savedSurfaceDescValid(false)
{
	d_size.d_x = 0, d_size.d_y = 0;
	d_dataSize.d_x = 0, d_dataSize.d_y = 0;
	d_texelScaling.d_x = 1.0, d_texelScaling.d_y = 1.0;
    loadFromFile(filename);
}

//----------------------------------------------------------------------------//
Texture::Texture(Renderer& owner, const Size& sz) :
    d_owner(owner),
    d_texture(0),
    d_savedSurfaceDescValid(false)
{
	d_size.d_x = 0, d_size.d_y = 0;
	d_dataSize.d_x = 0, d_dataSize.d_y = 0;
	d_texelScaling.d_x = 1.0, d_texelScaling.d_y = 1.0;

    Size tex_sz = d_owner.getAdjustedSize(sz.d_x, sz.d_y);

    HRESULT hr = D3DXCreateTexture(d_owner.getDevice(),
                                   static_cast<UINT>(tex_sz.d_x),
                                   static_cast<UINT>(tex_sz.d_y),
                                   1, 0, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED,
                                   &d_texture);

    if (FAILED(hr))
        throw RendererException(
            "Texture - Failed to create texture of specified size: "
            "D3D Texture creation failed.");

    updateTextureSize();
    updateCachedScaleValues();
}

//----------------------------------------------------------------------------//
Texture::Texture(Renderer& owner, LPDIRECT3DTEXTURE9 tex) :
    d_owner(owner),
    d_texture(0),
    d_savedSurfaceDescValid(false)
{
	d_size.d_x = 0, d_size.d_y = 0;
	d_dataSize.d_x = 0, d_dataSize.d_y = 0;
	d_texelScaling.d_x = 1.0, d_texelScaling.d_y = 1.0;
    setTexture(tex);
}

//----------------------------------------------------------------------------//
Texture::~Texture()
{
    cleanupTexture();
}

//----------------------------------------------------------------------------//
void Texture::setTexture(LPDIRECT3DTEXTURE9 tex)
{
    if (d_texture != tex)
    {
        cleanupTexture();
        d_dataSize.d_x = d_dataSize.d_y = 0;

        d_texture = tex;
        if (d_texture)
            d_texture->AddRef();
    }

    updateTextureSize();
    d_dataSize = d_size;
    updateCachedScaleValues();
}

//----------------------------------------------------------------------------//
LPDIRECT3DTEXTURE9 Texture::getTexture() const
{
    return d_texture;
}

//----------------------------------------------------------------------------//
const Size& Texture::getSize() const
{
    return d_size;
}

//----------------------------------------------------------------------------//
const Size& Texture::getOriginalDataSize() const
{
    return d_dataSize;
}

//----------------------------------------------------------------------------//
const Vector2& Texture::getTexelScaling() const
{
    return d_texelScaling;
}

//----------------------------------------------------------------------------//
void Texture::loadFromFile(const String& filename)
{
    // get and check existence of System object
    System* sys = System::getSingletonPtr();
    if (!sys)
        throw RendererException("Texture::loadFromFile - "
                                "System object has not been created!");

	D3DXIMAGE_INFO srcInfo;
	ZeroMemory(&srcInfo, sizeof(D3DXIMAGE_INFO));

    LPDIRECT3DDEVICE9 device = sys->getRenderer()->getDevice();
	HRESULT hr = D3DXCreateTextureFromFileEx(device, filename.c_str(), 0, 0, 2, D3DUSAGE_DYNAMIC, 
					D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, D3DX_DEFAULT, D3DX_DEFAULT, 0,
					&srcInfo, NULL, &d_texture);

    if (FAILED(hr))
        throw RendererException("Texture::loadFromFile - failed to load image '" + filename + "'.");

	d_dataSize.d_x = srcInfo.Width;
	d_dataSize.d_y = srcInfo.Height;

	updateTextureSize();
	updateCachedScaleValues();
}

//----------------------------------------------------------------------------//
void Texture::loadFromMemory(const void* buffer,
                                      const Size& buffer_size,
                                      PixelFormat pixel_format)
{
    cleanupTexture();

    // create a texture
    // TODO: Check resulting pixel format and react appropriately.
    D3DFORMAT pixfmt;
    switch (pixel_format)
    {
    case PF_RGB:
        pixfmt = D3DFMT_R8G8B8;
        break;
    case PF_RGBA:
        pixfmt = D3DFMT_A8B8G8R8;
        break;
    default:
        throw RendererException("Texture::loadFromMemory failed: "
                                "Invalid PixelFormat value specified.");
    }

    Size tex_sz = d_owner.getAdjustedSize(buffer_size.d_x, buffer_size.d_y);

    HRESULT hr = D3DXCreateTexture(d_owner.getDevice(),
                                   static_cast<UINT>(tex_sz.d_x),
                                   static_cast<UINT>(tex_sz.d_y),
                                   1, 0, pixfmt, D3DPOOL_MANAGED, &d_texture);

    if (FAILED(hr))
        throw RendererException("Texture::loadFromMemory failed: "
                                "Direct3D9 texture creation failed.");

    d_dataSize = buffer_size;
    updateTextureSize();
    updateCachedScaleValues();

    // lock the D3D texture
    D3DLOCKED_RECT hgeRect;
    hr = d_texture->LockRect(0, &hgeRect, 0, 0);

    if (FAILED(hr))
    {
        d_texture->Release();
        d_texture = 0;

        throw RendererException("Texture::loadFromMemory failed: "
                                "IDirect3DTexture9::LockRect failed.");
    }

    // copy data from buffer into texture
    ULONG* dst = static_cast<ULONG*>(hgeRect.pBits);
    const ULONG* src = static_cast<const ULONG*>(buffer);

    // RGBA
    if (pixel_format == PF_RGBA)
    {
        for (UINT i = 0; i < buffer_size.d_y; ++i)
        {
            for (UINT j = 0; j < buffer_size.d_x; ++j)
            {
                // we dont need endian safety on microsoft
                UCHAR r = static_cast<UCHAR>(src[j] & 0xFF);
                UCHAR g = static_cast<UCHAR>((src[j] >> 8) & 0xFF);
                UCHAR b = static_cast<UCHAR>((src[j] >> 16)  & 0xFF);
                UCHAR a = static_cast<UCHAR>((src[j] >> 24) & 0xFF);

                dst[j] = D3DCOLOR_ARGB(a, r, g, b);
            }

            dst += hgeRect.Pitch / sizeof(ULONG);
            src += static_cast<ULONG>(buffer_size.d_x);
        }
    }
    // RGB
    else
    {
        for (UINT i = 0; i < buffer_size.d_y; ++i)
        {
            for (UINT j = 0; j < buffer_size.d_x; ++j)
            {
                UCHAR r = reinterpret_cast<const UCHAR*>(src)[j * 3];
                UCHAR g = reinterpret_cast<const UCHAR*>(src)[j * 3 + 1];
                UCHAR b = reinterpret_cast<const UCHAR*>(src)[j * 3 + 2];
                UCHAR a = 0xFF;

                dst[j] = D3DCOLOR_ARGB(a, r, g, b);
            }

            dst += hgeRect.Pitch / sizeof(ULONG);
            src = reinterpret_cast<const ULONG*>
                  (reinterpret_cast<const UCHAR*>(src) +
                   static_cast<int>(buffer_size.d_x) * 3);
        }
    }

    d_texture->UnlockRect(0);
}

//----------------------------------------------------------------------------//
void Texture::saveToMemory(void* buffer)
{
    // TODO:
    throw RendererException("Texture::saveToMemory - Unimplemented!");
}

//----------------------------------------------------------------------------//
void Texture::cleanupTexture()
{
    if (d_texture)
    {
        d_texture->Release();
        d_texture = 0;
    }
}

//----------------------------------------------------------------------------//
void Texture::updateCachedScaleValues()
{
    //
    // calculate what to use for x scale
    //
    const float orgW = d_dataSize.d_x;
    const float texW = d_size.d_x;

    // if texture and original data width are the same, scale is based
    // on the original size.
    // if texture is wider (and source data was not stretched), scale
    // is based on the size of the resulting texture.
    d_texelScaling.d_x = 1.0f / ((orgW == texW) ? orgW : texW);

    //
    // calculate what to use for y scale
    //
    const float orgH = d_dataSize.d_y;
    const float texH = d_size.d_y;

    // if texture and original data height are the same, scale is based
    // on the original size.
    // if texture is taller (and source data was not stretched), scale
    // is based on the size of the resulting texture.
    d_texelScaling.d_y = 1.0f / ((orgH == texH) ? orgH : texH);
}

//----------------------------------------------------------------------------//
void Texture::updateTextureSize()
{
    D3DSURFACE_DESC surfDesc;

    // obtain details of the size of the texture
    if (d_texture && SUCCEEDED(d_texture->GetLevelDesc(0, &surfDesc)))
    {
        d_size.d_x  = surfDesc.Width;
        d_size.d_y = surfDesc.Height;
    }
    // use the original size if query failed.
    // NB: This should probably be an exception.
    else
        d_size = d_dataSize;
}

//----------------------------------------------------------------------------//
void Texture::setOriginalDataSize(UINT width, UINT height)
{
    d_dataSize.d_x = width, d_dataSize.d_y = height;
    updateCachedScaleValues();
}

//----------------------------------------------------------------------------//
void Texture::preD3DReset()
{
    // if already saved surface info, or we have no texture, do nothing
    if (d_savedSurfaceDescValid || !d_texture)
        return;

    // get info about our texture
    d_texture->GetLevelDesc(0, &d_savedSurfaceDesc);

    // if texture is managed, we have nothing more to do
    if (d_savedSurfaceDesc.Pool == D3DPOOL_MANAGED)
        return;

    // otherwise release texture.
    d_texture->Release();
    d_texture = 0;
    d_savedSurfaceDescValid = true;
}

//----------------------------------------------------------------------------//
void Texture::postD3DReset()
{
    // if texture has no saved surface info, we do nothing.
    if (!d_savedSurfaceDescValid)
        return;

    // otherwise, create a new texture using saved details.
    d_owner.getDevice()->
        CreateTexture(d_savedSurfaceDesc.Width,
                      d_savedSurfaceDesc.Height,
                      1, d_savedSurfaceDesc.Usage, d_savedSurfaceDesc.Format,
                      d_savedSurfaceDesc.Pool, &d_texture, 0);

    d_savedSurfaceDescValid = false;
}